﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using SimpleServiceBus.Bus.MessageManagement.MessageHandling;
using SimpleServiceBus.Utilities;

namespace SimpleServiceBus.Bus.MessageManagement.MessageHandling
{
    public class DefaultMessageHandlerResolutionService : IMessageHandlerResolutionService
    {
        readonly ReaderWriterLock _rwLock = new ReaderWriterLock();
        private readonly ReaderWriterLock _cacheLock = new ReaderWriterLock();
        private Dictionary<string, bool> _registeredAssemblies = new Dictionary<string, bool>();

        public DefaultMessageHandlerResolutionService()
        {
            MessageHandlerBindings = new List<IMessageHandlerBinding>();
            HandlerCache = new Dictionary<Type, IMessageHandler[]>();
        }

        protected List<IMessageHandlerBinding> MessageHandlerBindings { get; private set; }
        protected Dictionary<Type, IMessageHandler[]> HandlerCache;

        public void RegisterMessageHandlerAssembly(Assembly asm)
        {
            if (asm == null || asm.FullName == null)
                throw new ArgumentNullException("asm");

            using (new ReaderLock(_rwLock,TimeSpan.FromSeconds(30)))
            {
                if (_registeredAssemblies.ContainsKey(asm.FullName))
                    return;
            }

            using (new WriterLock(_rwLock,TimeSpan.FromSeconds(30)))
            {
                _registeredAssemblies.Add(asm.FullName, true);
                foreach (Type type in asm.GetTypes())
                {
                    if (type.IsMessageHandler())
                    {
                        RegisterMessageHandler(type);
                    }
                }
            }
          
        }

        public void RegisterMessageHandler(Type messageHandlerType)
        {
            IList<Type> handledTypes = messageHandlerType.GetAllMessageTypesHandled();
            foreach (Type handledType in handledTypes)
            {
                var binding = new MessageHandlerBinding(messageHandlerType, handledType);

                using (new WriterLock(_rwLock, TimeSpan.FromSeconds(30)))
                {
                    MessageHandlerBindings.Add(binding);
                }
            }
        }

        public IMessageHandler[] ResolveHandlersFor(MessageEnvelope messageEnvelope)
        {
            if (messageEnvelope.Body == null) return new IMessageHandler[0];
            Type messageType = messageEnvelope.Body.GetType();

            var handlers = CheckCache(messageType);
            if (handlers != null)
                return handlers;

            handlers = FetchHandlers(messageEnvelope);

            CacheHandlers(messageType,handlers);
           
            return handlers;
        }

        protected virtual IMessageHandler[] CheckCache(Type messageType)
        {
            using (new ReaderLock(_cacheLock, TimeSpan.FromSeconds(10)))
            {
                IMessageHandler[] handlers;
                HandlerCache.TryGetValue(messageType, out handlers);
                return handlers;
            }
        }

        protected virtual IMessageHandler[] FetchHandlers(MessageEnvelope messageEnvelope)
        {
            var handlerList = new List<IMessageHandler>();

            using (new ReaderLock(_rwLock, TimeSpan.FromSeconds(10)))
            {
                foreach (IMessageHandlerBinding binding in MessageHandlerBindings)
                {
                    if (binding.Matches(messageEnvelope))
                        handlerList.Add(binding);
                }
            }

            return handlerList.ToArray();
        }

        protected virtual void CacheHandlers(Type messageType,IMessageHandler[] handlers)
        {
            using (new WriterLock(_cacheLock, TimeSpan.FromSeconds(10)))
            {
                HandlerCache[messageType] = handlers;
            }
        }
    }
}